package web

import (
	"fmt"
	"gitee.com/DonHz/basic-go/webook/internal/domain"
	"gitee.com/DonHz/basic-go/webook/internal/service"
	ijwt "gitee.com/DonHz/basic-go/webook/internal/web/jwt"
	"gitee.com/DonHz/basic-go/webook/pkg/ginx"
	"gitee.com/DonHz/basic-go/webook/pkg/logger"
	"github.com/dlclark/regexp2"
	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
	"net/http"
	"time"
)

const biz = "login"

// 确保UserHandler实现了handler接口
var _ handler = (*UserHandler)(nil)

// 所有与 user 有关的路由 : 注册、登录、查看、编辑用户信息
type UserHandler struct {
	userSvc     service.UserService
	codeSvc     service.CodeService
	emailExp    *regexp2.Regexp
	passwordExp *regexp2.Regexp
	birthdayExp *regexp2.Regexp
	phoneExp    *regexp2.Regexp
	ijwt.Handler
	cmd redis.Cmdable
	l   logger.Logger
}

func NewUserHanlder(svc service.UserService, codeSvc service.CodeService,
	cmd redis.Cmdable, jwtHandler ijwt.Handler, l logger.Logger) *UserHandler {
	const (
		emailRegexPattern = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"
		// 密码包含至少一位数字，字母和特殊字符,且长度8-16
		passwordRegexPattern = "^(?![0-9a-zA-Z]+$)(?![a-zA-Z!@#$%^&*]+$)(?![0-9!@#$%^&*]+$)[0-9A-Za-z!@#$%^&*]{8,16}$"
		// 生日格式：2001-01-01
		birthdayRegexPattern = "^\\d{4}-\\d{2}-\\d{2}$"
		// 手机号：1开头，11位
		phoneRegexPattern = "^1[345789]{1}\\d{9}$"
	)
	// 验证邮箱格式
	emailExp := regexp2.MustCompile(emailRegexPattern, regexp2.None)
	// 验证密码格式
	passwordExp := regexp2.MustCompile(passwordRegexPattern, regexp2.None)
	// 验证出生日期格式
	birthdayExp := regexp2.MustCompile(birthdayRegexPattern, regexp2.None)
	// 验证手机号
	phoneExp := regexp2.MustCompile(phoneRegexPattern, regexp2.None)
	return &UserHandler{
		userSvc:     svc,
		emailExp:    emailExp,
		passwordExp: passwordExp,
		birthdayExp: birthdayExp,
		phoneExp:    phoneExp,
		codeSvc:     codeSvc,
		Handler:     jwtHandler,
		cmd:         cmd,
		l:           l,
	}
}

func (u *UserHandler) RegisterRoutes(r *gin.Engine) {
	ug := r.Group("/users")
	ug.POST("/signup", u.SignUp)     // 注册
	ug.POST("/login", u.LoginJWT)    // 登录
	ug.POST("/logout", u.LogOutJWT)  // 登录
	ug.GET("/profile", u.ProfileJWT) // 查看
	ug.POST("/edit", u.Edit)         // 编辑
	// PUT "/login/sms/code" 发验证码
	// POST "/login/sms/code" 校验验证码
	ug.POST("/login_sms/code/send", u.SendLoginSMSCode)
	ug.POST("/login_sms", ginx.WrapBody[LoginSMSReq](
		u.l.With(logger.String("method", "login_sms")), u.LoginSMS))
	ug.POST("/refresh_token", u.RefreshToken)
}

func (u *UserHandler) SignUp(ctx *gin.Context) {
	type SignUpReq struct {
		Email           string `json:"email"`
		ConfirmPassword string `json:"confirmPassword"`
		Password        string `json:"password"`
	}

	var req SignUpReq
	// Bind 方法会根据 Content-Type 来解析数据到req里面
	// 解析错误会返回一个 400 的错误
	if err := ctx.Bind(&req); err != nil {
		return
	}

	// 验证邮箱格式
	ok, err := u.emailExp.MatchString(req.Email)
	if err != nil {
		// 记录日志
		ctx.String(http.StatusOK, "系统错误，请稍后再试")
		return
	}
	if !ok {
		// 记录日志
		ctx.String(http.StatusOK, "你的邮箱格式不对，请检查")
		return
	}

	// 验证密码格式
	ok, err = u.passwordExp.MatchString(req.Password)
	if err != nil {
		// 记录日志
		ctx.String(http.StatusOK, "系统错误，请稍后再试")
		return
	}
	if !ok {
		// 记录日志
		ctx.String(http.StatusOK, "密码包含至少一位数字，字母和特殊字符,且长度8-16")
		return
	}

	// 验证两次输入密码是否一致
	if req.ConfirmPassword != req.Password {
		// 记录日志
		ctx.String(http.StatusOK, "两次输入密码不一致")
		return
	}
	// 调用 svc的方法
	err = u.userSvc.SignUp(ctx, domain.User{
		Email:    req.Email,
		Password: req.Password,
	})
	if err == service.ErrUserDuplicateEmail {
		ctx.String(http.StatusOK, "邮箱冲突")
		return
	}

	if err != nil {
		ctx.String(http.StatusOK, "系统异常")
		return
	}
	ctx.String(http.StatusOK, "注册成功")
	fmt.Printf("%v", req)
	// 接下来是数据库操作
}

func (u *UserHandler) LoginJWT(ctx *gin.Context) {
	type LoginReq struct {
		Email    string `json:"email"`
		Password string `json:"password"`
	}

	var req LoginReq
	if err := ctx.Bind(&req); err != nil {
		return
	}
	user, err := u.userSvc.Login(ctx, domain.User{
		Email:    req.Email,
		Password: req.Password,
	})
	if err == service.ErrInvalidUserOrPassword {
		ctx.String(http.StatusOK, "用户名或密码不对")
		return
	}
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	// 步骤2 登陆成功，
	// 设置JWT登录态，生成JWT token
	if err := u.SetLoginToken(ctx, user.Id); err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	ctx.String(http.StatusOK, "登陆成功")
	return
}

// 已经提取到jwt.go中
//func (u *UserHandler) setJWTToken(ctx *gin.Context, uid int64) error {
//	claims := UserClaims{
//		RegisteredClaims: jwt.RegisteredClaims{
//			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute)),
//		},
//		Uid:       uid,
//		UserAgent: ctx.Request.UserAgent(),
//	}
//	token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
//	tokenStr, err := token.SignedString([]byte("qJfZR1psrw3PXpT1aPbeRoUl0eLtdBH2bcFobbf9MKhdbd8MoR2V046H0obQd2tc"))
//	if err != nil {
//		return err
//	}
//	fmt.Println("token: ", token)
//	fmt.Println("tokenStr: ", tokenStr)
//	// 返回给前端
//	ctx.Header("x-jwt-token", tokenStr)
//	return nil
//}

type LoginSMSReq struct {
	Phone string `json:"phone"`
	Code  string `json:"code"`
}

func (u *UserHandler) LoginSMS(ctx *gin.Context, req LoginSMSReq) (ginx.Result, error) {

	ok, err := u.codeSvc.Verify(ctx, biz, req.Phone, req.Code)
	if err != nil {
		if err == service.ErrCodeVerifyTooMany {
			ctx.JSON(http.StatusOK, Result{
				Code: 4,
				Msg:  "系统错误，请稍后再试",
			})
			zap.L().Warn("用户校验次数过多", zap.Error(err))
		}
		//ctx.JSON(http.StatusOK, Result{
		//	Code: 5,
		//	Msg:  "系统错误",
		//})
		zap.L().Error("校验验证码出错", zap.Error(err))
		return Result{Code: 5, Msg: "系统错误"}, fmt.Errorf("用户手机号码登陆失败，%w", err)
	}
	if !ok {
		//ctx.JSON(http.StatusOK, Result{
		//	Code: 4,
		//	Msg:  "验证码输入错误，请重新输入",
		//})
		return Result{Code: 4, Msg: "验证码输入错误，请重新输入"}, nil
	}
	user, err := u.userSvc.FindOrCreate(ctx, req.Phone)
	if err != nil {
		//ctx.JSON(http.StatusOK, Result{
		//	Code: 5,
		//	Msg:  "系统错误，请稍后再试",
		//})
		zap.L().Error("通过手机号查找或创建用户失败", zap.Error(err))
		return Result{Code: 5, Msg: "系统错误，请稍后再试"}, fmt.Errorf("登录或注册用户失败，%w", err)
	}
	if err = u.SetLoginToken(ctx, user.Id); err != nil {
		//ctx.JSON(http.StatusOK, Result{
		//	Code: 5,
		//	Msg:  "系统错误，请稍后再试",
		//})
		zap.L().Error("设置长短token失败", zap.Error(err),
			zap.String("method", "UserHandler: LoginSMS"))
		return Result{Code: 5, Msg: "系统错误，请稍后再试"}, err
	}
	//ctx.JSON(http.StatusOK, Result{
	//	Code: 5,
	//	Msg:  "验证码校验通过",
	//})
	return Result{Code: 5, Msg: "验证码校验通过"}, nil
}

func (u *UserHandler) SendLoginSMSCode(ctx *gin.Context) {
	type Req struct {
		Phone string `json:"phone"`
	}
	var req Req
	if err := ctx.Bind(&req); err != nil {
		return
	}
	// 是不是一个合法手机号
	ok, err := u.phoneExp.MatchString(req.Phone)
	if err != nil {
		// 记录日志
		ctx.JSON(http.StatusOK, Result{
			Code: 5,
			Msg:  "系统错误，请稍后再试",
		})
		return
	}
	if !ok {
		// 记录日志
		ctx.JSON(http.StatusOK, Result{
			Code: 4,
			Msg:  "手机号码输入错误，请重新输入",
		})
		return
	}
	err = u.codeSvc.Send(ctx, biz, req.Phone)
	switch err {
	case nil:
		ctx.JSON(http.StatusOK, Result{
			Msg: "发送成功",
		})
	case service.ErrCodeSendTooMany:
		ctx.JSON(http.StatusOK, Result{
			Code: 4,
			Msg:  "发送太频繁，请稍后再试",
		})
	default:
		ctx.JSON(http.StatusOK, Result{
			Code: 5,
			Msg:  "系统错误",
		})
	}
}

func (u *UserHandler) Login(ctx *gin.Context) {
	type LoginReq struct {
		Email    string `json:"email"`
		Password string `json:"password"`
	}
	var req LoginReq
	if err := ctx.Bind(&req); err != nil {
		return
	}
	user, err := u.userSvc.Login(ctx, domain.User{
		Email:    req.Email,
		Password: req.Password,
	})
	if err == service.ErrInvalidUserOrPassword {
		ctx.String(http.StatusOK, "用户名或密码不对")
		return
	}
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}

	// 步骤2 登陆成功，设置session
	// 取出session
	sess := sessions.Default(ctx)
	//设置在session中的值
	sess.Set("userId", user.Id)
	// 控制cookie
	sess.Options(sessions.Options{
		//Secure: true,
		//HttpOnly: true,
		// 过期时间：一分钟
		MaxAge: 60, // 时间到了之后，会退出需要重新登陆
	})
	err = sess.Save()
	if err != nil {
		ctx.String(http.StatusOK, "session保存失败，系统错误")
		return
	}
	ctx.String(http.StatusOK, "登陆成功")
	return
}

func (u *UserHandler) LogOut(ctx *gin.Context) {
	sess := sessions.Default(ctx)
	sess.Options(sessions.Options{
		MaxAge: -1,
	})
	sess.Save()
	ctx.String(http.StatusOK, "退出登陆成功")
}
func (u *UserHandler) LogOutJWT(ctx *gin.Context) {
	err := u.ClearToken(ctx)
	if err != nil {
		ReturnError(ctx, 5, "退出登陆失败")
		return
	}
	ReturnSuccess(ctx, 200, "成功退出登录", nil, 1)
}

func (u *UserHandler) Edit(ctx *gin.Context) {
	// 修改用户非重要信息
	type Req struct {
		Nickname string `json:"nickname"`
		Birthday string `json:"birthday"`
		AbouteMe string `json:"aboutMe"`
	}
	var req Req
	if err := ctx.Bind(&req); err != nil {
		ReturnError(ctx, 200, "请求获取异常")
	}
	// 校验字段
	if req.Nickname == "" {
		ctx.JSON(http.StatusOK, Result{
			Code: 4,
			Msg:  "昵称不能为空",
		})
	}
	if len(req.AbouteMe) > 1024 {
		ctx.JSON(http.StatusOK, Result{
			Code: 4,
			Msg:  "输入过长",
		})
	}
	// 验证生日格式
	birthday, err := time.Parse(time.DateOnly, req.Birthday)
	if err != nil {
		ctx.JSON(http.StatusOK, Result{
			Code: 4,
			Msg:  "生日格式不对，应该是YYYY-MM-DD",
		})
		return
	}

	//身份校验
	//if err := ctx.Bind(&req); err != nil {
	//	ReturnError(ctx, 200, "获取请求异常")
	//	return
	//}
	//// 获取用户id
	//sess := sessions.Default(ctx)
	//if sess == nil {
	//	ctx.AbortWithStatus(http.StatusUnauthorized)
	//	return
	//}
	//id := sess.Get("userId")
	//if id == nil {
	//	ctx.AbortWithStatus(http.StatusUnauthorized)
	//	return
	//}
	uc := ctx.MustGet("userId")
	// 更新用户信息
	err = u.userSvc.UpdateNonSensitiveInfo(ctx, domain.User{
		Id:       uc.(int64),
		Nickname: req.Nickname,
		Birthday: birthday,
		AboutMe:  req.AbouteMe,
	})
	if err != nil {
		ctx.String(http.StatusOK, "系统异常")
		return
	}
	ctx.String(http.StatusOK, "更新成功")
}

func (u *UserHandler) ProfileJWT(ctx *gin.Context) {
	// 定义Profile结构体的原因是不能把domain中的user暴露出去，会有风险 password
	type Profile struct {
		Nickname string `json:"nickname"`
		Email    string `json:"email"`
		Phone    string `json:"phone"`
		Birthday string `json:"birthday"`
		AboutMe  string `json:"aboutMe"`
	}
	c, ok := ctx.Get("userId")
	if !ok {
		ctx.String(http.StatusOK, "系统异常")
		return
	}
	//fmt.Println("c: ",c)
	// 类型断言 any -> *UserClaims
	//claims, ok := c.(*ijwt.UserClaims)
	//if !ok {
	//	ctx.String(http.StatusOK, "系统异常")
	//	return
	//}
	//fmt.Println("claims.Uid: ", claims.Uid)
	//fmt.Println("ctx.userId: ", c)
	user, err := u.userSvc.Profile(ctx, c.(int64))
	if err != nil {
		// 按道理来说 Uid对应的数据肯定存在
		ctx.String(http.StatusOK, "系统异常")
		return
	}
	ctx.JSON(http.StatusOK, Profile{
		Email:    user.Email,
		Phone:    user.Phone,
		Nickname: user.Nickname,
		Birthday: user.Birthday.Format(time.DateOnly),
		AboutMe:  user.AboutMe,
	})
}

func (u *UserHandler) Profile(ctx *gin.Context) {
	sess := sessions.Default(ctx)
	if sess == nil {
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}
	id := sess.Get("userId")
	if id == nil {
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}
	user, err := u.userSvc.Profile(ctx, id.(int64))
	if err != nil {
		ctx.String(http.StatusOK, "系统异常")
		return
	}
	type Profile struct {
		Nickname string `json:"nickname"`
		Email    string `json:"email"`
		Phone    string `json:"phone"`
		Birthday string `json:"birthday"`
		AboutMe  string `json:"aboutMe"`
	}
	ctx.JSON(http.StatusOK, Profile{
		Nickname: user.Nickname,
		Email:    user.Email,
		Phone:    user.Phone,
		Birthday: user.Birthday.Format(time.DateOnly),
		AboutMe:  user.AboutMe,
	})
}

func (u *UserHandler) AuthURL(ctx *gin.Context) {
	ctx.JSON(http.StatusOK, Result{
		Code: 200,
		Msg:  "获取成功",
	})
}

func (u *UserHandler) RefreshToken(ctx *gin.Context) {
	// 这个地方是 refreshToken
	refreshToken := u.ExtractToken(ctx)
	var rc ijwt.RefreshClaims
	token, err := jwt.ParseWithClaims(refreshToken, &rc, func(token *jwt.Token) (interface{}, error) {
		return ijwt.Refresh_token, nil
	})
	if err != nil || !token.Valid {
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}
	err = u.CheckSession(ctx, rc.Ssid)
	if err != nil {
		// redis有问题或者已经退出
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}
	// 给一个新的token
	err = u.SetJWTToken(ctx, rc.Uid, rc.Ssid)
	if err != nil {
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}
	ReturnSuccess(ctx, 200, "刷新access_token", "", 1)
}
